/*
 * Copyright (c) 2009 Johnny Mast
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#import "TileMap.h"
#import "SpriteSheet.h"
#import "TileSet.h"
#import "Image.h"
#import "layer.h"



@interface TileMap  (Parser)
	-(void)parseMapFile:(NSString*)fileContents;
	-(TileSet*)findTileSetWithGlobalID:(int)GlobalID;
	-(Layer*)getLayer:(NSInteger)layerID;
	-(TileSet*)getTitleSet:(NSInteger)TileSetID;
	-(XFMapSpriteLocation*)calculateSpriteLocation:(NSInteger)globalID tileSet:(TileSet*)tileSet;
@end

@implementation TileMap
@synthesize mapProperties     = _mapProperties;
@synthesize cachedTiles       = _cachedTiles;
@synthesize tileSetProperties = _tileSetProperties;
@synthesize mapName           = _mapName;
@synthesize mapHeight         = _mapHeight;
@synthesize mapWidth          = _mapWidth;
@synthesize tileHeight        = _tileHeight;
@synthesize tileWidth         = _tileWidth;
@synthesize currentLayerID    = _currentLayerID;
@synthesize currentTileSetID  = _currentTileSetID;
@synthesize currentTileSet    = _currentTileSet;
@synthesize layers            = _layers;
@synthesize tileSets          = _tileSets;
@synthesize tileX             = _tileX;
@synthesize tileY             = _tileY;
@synthesize font              = _font;

- (id)initWithTiledFile:(NSString*)mapname fileExtension:(NSString*)aFileExtension {
	
	self = [super init];
	if (self != nil) {
		_mapName = mapname;
		
		NSString *mapFilename    = [[NSBundle mainBundle] pathForResource:_mapName ofType:aFileExtension];
				
		if(![[NSFileManager defaultManager] fileExistsAtPath:mapFilename])
		{
#if defined(GAMENGINE_DEBUG)
			NSLog(@"HieroBitmapFont: Error, Could not find font file");
#endif
		}
		
		NSData*   maploader  = [NSData dataWithContentsOfFile:mapFilename];
		NSString* mapFileContent =  [[NSString alloc] initWithData:maploader encoding:NSUTF8StringEncoding];	
		
		[self parseMapFile:mapFileContent];
		[mapFileContent release];
	}
	return self;
}

-(void)renderAtPoint:(CGPoint)point mapX:(NSInteger)mapX mapY:(NSInteger)mapY tilesInWidth:(NSInteger)tilesInWidth tilesInHeight:(NSInteger)tilesInHeight layer:(NSInteger)layerID {
	Layer* thisLayer = [self getLayer:layerID];
	
	
if (thisLayer)
	{
		
	//	NSLog(@"Doing map %@", [thisLayer layerName]);
		
		NSInteger X       = mapX;
		NSInteger XBackup = mapX;
		NSInteger Y       = mapY;
		
		int startx  = point.x;
		int cnt     = 0;
		
		if (X < 0) {
			X = 0;
			XBackup = X;
		}
		
		if (X + tilesInWidth  > [self mapWidth])  {
			X -= (X + tilesInWidth) - [self mapWidth];
			XBackup = X;
		}
		

		if (Y < 0) 	{
			Y = 0;
		}
		
		if (Y + tilesInHeight  > [self mapHeight]) 	{
			Y -= (Y + tilesInHeight) - [self mapHeight];
		}
		
		for(int yy = 0; yy < tilesInWidth; yy++)
		{
			
						
			for (int xx = 0; xx < tilesInWidth; xx++)
			{
				
				NSInteger globalID     = [thisLayer getGlobalTileIDAtX:X y:Y];		
				NSInteger tileSetID    = [thisLayer getTileSetIDAtX:X y:Y];
				NSString *textureName  = [NSString stringWithFormat:@"tilemap_gid_%d", globalID];

				Image *tileImage = nil;
				
				TileSet* tileset = [self getTitleSet:tileSetID];
				
				if (!([_cachedTiles objectForKey:textureName]))
				{
					if (tileset && globalID > 0)
					{
						XFMapSpriteLocation* tileSpriteLocation = [self calculateSpriteLocation:globalID-1 tileSet:tileset];
						
						if (tileSpriteLocation)
						{
						
							
							tileImage = [[tileset tiles] getImageAtRow:tileSpriteLocation->row col:tileSpriteLocation->col];
							
							[_cachedTiles setObject:tileImage
											 forKey:textureName];
							
							[tileImage renderAtPoint:point centerOfImage:NO];
							free(tileSpriteLocation);
							
						} else {
							NSLog(@"tileSpriteLocation is null (%d/%d)", tileSetID, globalID);
						}
					}
				} else {
					
					tileImage = [_cachedTiles objectForKey:textureName];
					[tileImage renderAtPoint:point centerOfImage:NO];
				}
				
				
				if (tilesInWidth == cnt)
				{
					
					
					X         = XBackup;
					Y++;
					point.y -= [self tileHeight];
					point.x  = startx;
					cnt      = 0;
					
				} else {
					cnt++ ;
					X++;
					point.x  += [self tileWidth];
				}
			}
			
			
		}
	} else {
		NSLog(@"Error layer not found");
	}


}


-(void)renderGrid:(CGPoint)point withFont:(NSString*)fontname mapX:(NSInteger)mapX mapY:(NSInteger)mapY tilesInWidth:(NSInteger)tilesInWidth tilesInHeight:(NSInteger)tilesInHeight {
		
	NSInteger X       = mapX;
	NSInteger XBackup = mapX;
	NSInteger Y       = mapY;

	int startx  = point.x;
	int cnt     = 0;

	
	for(int yy = 0; yy < tilesInWidth; yy++)
	{
		for (int xx = 0; xx < tilesInWidth; xx++)
		{
			if (!_font)
				_font = [[HieroBitmapFont alloc] initWithFontName:fontname filter:GL_NEAREST scale:0.5f];
			
			[_font renderTextAtPoint:point text:[NSString stringWithFormat:@"(%d.%d)",X,Y]];
			
			if (tilesInWidth == cnt)
			{
				X         = XBackup;
				Y++;
				point.y -= [self tileHeight];
				point.x  = startx;
				cnt      = 0;
			} else {
				cnt++ ;
				X++;
				point.x  += [self tileWidth];
			}
			
		}
		 
	}
}

- (NSString*)getTilePropertyForGlobalTileID:(int)aGlobalTileID key:(NSString*)aKey defaultValue:(NSString*)aDefaultValue {
	NSString *value = [[_tileSetProperties valueForKey:[NSString stringWithFormat:@"%d", aGlobalTileID]] valueForKey:aKey];
	if(!value)
		return aDefaultValue;
	return value;
}


@end






@implementation TileMap (Parser)

/* Simple sum to calculate the row of where this image 
 ** can be found at in the SpriteSheet.
 */

-(XFMapSpriteLocation*)calculateSpriteLocation:(NSInteger)globalID tileSet:(TileSet*)tileSet {
	
	if (!tileSet) 
		return nil;
	
	SpriteSheet* _currentTiles = [tileSet tiles];
	
	if (!tileSet || !_currentTiles) 
	{
		if (_currentTiles == nil) NSLog(@"_currentTiles == nill");
		if (tileSet == nil) NSLog(@"tileSet == nill");
		
		return nil;
	}
	NSInteger _tw           = [tileSet tileWidth];
	NSInteger _th           = [tileSet tileHeight];
	NSInteger itemsInWidth  = ((int)_currentTiles.SpriteSheetImage.size.width) / (_tw)  ;
	NSInteger itemsInHeight = ((int)_currentTiles.SpriteSheetImage.size.height) / (_th) ;
	NSInteger imgcnt        = 0;
	
	XFMapSpriteLocation *pos = malloc(sizeof(XFMapSpriteLocation));
	
	pos->row = 0;
	pos->col = 0;
	
	for(int yy = 0; yy < itemsInHeight; yy++)
	{
		for (int xx = 0; xx < itemsInWidth; xx++)
		{
			if (imgcnt == globalID)
			{
				pos->col = xx;
				pos->row = yy; 
				return pos;
			}
			imgcnt++;
		}
	}
	return nil;
}


- (TileSet*)findTileSetWithGlobalID:(int)GlobalID {
	// Loop through all the tile sets we have and check to see if the supplied global ID
	// is within one of those tile sets.  If the global ID is found then return the tile set
	// in which it was found
	for(TileSet *tileSet in _tileSets) {
		if([tileSet containsGlobalID:GlobalID]) {
			return tileSet;
		}
	}
	return nil;
}

-(Layer*)getLayer:(NSInteger)layerID {
	// Loop through all layers that we have in store and see if we can identify the
	// requested layer. If this is the case we return this object or else nil will be 
	// returned.
	for(Layer *layer in _layers) {
		if([layer layerID] == layerID) {
			return layer;
		}
	}
	return nil;	
}

-(TileSet*)getTitleSet:(NSInteger)TileSetID {
 // Loop through all layers that we have in store and see if we can identify the
 // requested layer. If this is the case we return this object or else nil will be 
 // returned.
 for(TileSet *tileSet in _tileSets) {
	 if([tileSet tileSetID] == TileSetID) {
		 return tileSet;
	 }
 }
 return nil;	
}


-(BOOL)errorCheck:(NSError*)error {
	if(error) {
#if defined(GAMENGINE_DEBUG)
		NSLog(@"GameEngine: (TileMap::errorCheck) %@", error);
#endif
		return YES;
	}
	return NO;
}

-(void)parseMapFile:(NSString*)fileContents {
	
	
	@try {
		

		NSError *error;
		_mapProperties     = [[NSMutableDictionary alloc] init];
		_tileSetProperties = [[NSMutableDictionary alloc] init];
		_cachedTiles       = [[NSMutableDictionary alloc] init];
		_layers            = [[NSMutableArray alloc] init];
		_tileSets          = [[NSMutableArray alloc] init];
		_currentLayerID    = 0;
		_currentTileSetID  = 0;
		_tileX = 0;
		_tileY = 0;
		
		
		DDXMLDocument *theXMLDocument = [[DDXMLElement alloc] initWithXMLString:fileContents error:&error];
		
		if ([self errorCheck:error]) 
			return;
		
		NSArray *properties = [theXMLDocument nodesForXPath:@"/map/properties/property" error:&error];
		
		if ([self errorCheck:error]) 
			return;		
		
		/*
		** Parsing extra properties / information
		** stored inside the mapfile.
		*/
		for(DDXMLElement *property in properties) 
		{
			NSString *name = [[property attributeForName:@"name"] stringValue];
			NSString *value = [[property attributeForName:@"value"] stringValue];
			[_mapProperties setObject:value forKey:name];
#if defined(GAMENGINE_DEBUG)
			NSLog(@"Tiled: Tilemap property '%@' found with value '%@'", name, value);
#endif
		}
		

		/*
		 ** Parsing map and tile properties / information
		 ** stored inside the mapfile.
		 */		
		NSArray *mapElements = [theXMLDocument nodesForXPath:@"/map" error:&error];
		
		if ([self errorCheck:error]) 
			return;
		
		DDXMLElement *mapElement = [mapElements objectAtIndex:0];
		
		_mapWidth   = [[[mapElement attributeForName:@"width"] stringValue] intValue];
		_mapHeight  = [[[mapElement attributeForName:@"height"] stringValue] intValue];
		_tileWidth  = [[[mapElement attributeForName:@"tilewidth"] stringValue] intValue];
		_tileHeight = [[[mapElement attributeForName:@"tileheight"] stringValue] intValue];
		

		// Process the tileset elements and read the attributes we need.		
		NSArray *tileSetElements = [theXMLDocument nodesForXPath:@"/map/tileset" error:nil];
		
		
		for(DDXMLElement *tileSetElement in tileSetElements) {
			
			NSString *tileSetName     = [[tileSetElement  attributeForName:@"name"] stringValue];
			NSInteger tileSetWidth    = [[[tileSetElement attributeForName:@"tilewidth"] stringValue] intValue];
			NSInteger tileSetHeight   = [[[tileSetElement attributeForName:@"tileheight"] stringValue] intValue];
			NSInteger tileSetFirstGID = [[[tileSetElement attributeForName:@"firstgid"] stringValue] intValue];
			NSInteger tileSetSpacing  = [[[tileSetElement attributeForName:@"spacing"] stringValue] intValue];
			
			NSArray *imageElements = [tileSetElement nodesForXPath:@"/tileset/image" error:nil];
			NSString *source = [[[imageElements objectAtIndex:0] attributeForName:@"source"] stringValue];
			
			NSLog(@"TileSet: TILESET found named: %@, width=%d, height=%d, firstgid=%d, spacing=%d, id=%d image=%@", 
				  tileSetName, 
				  tileSetWidth, 
				  tileSetHeight, 
				  tileSetFirstGID, 
				  tileSetSpacing, 
				  _currentTileSetID,
				  source);
			
			// Create a tileset instance based on the retrieved information
			_currentTileSet = [[TileSet alloc] initWithImageNamed:source 
															name:tileSetName 
													   tileSetID:_currentTileSetID 
														firstGID:tileSetFirstGID 
													   tileWidth:tileSetWidth 
													  tileHeight:tileSetHeight 
														 spacing:tileSetSpacing];
			
			// Add the tileset instance we have just created to the array of tilesets
			[_tileSets addObject:_currentTileSet];
			
			// Release the current tileset instance as its been added to the array and we do not need it now
			[_currentTileSet release];
			
			_currentTileSetID++;
			
			/*
			** Set see if this tile set has information about 
			** special tiles used in this map. This information could be
			** for some kind of importance to the gameplay so we need
			** to import this information as well.
			*/
			NSArray *tilesetInformation = [tileSetElement nodesForXPath:@"/tileset/tile" error:nil];
			
			for (DDXMLElement* tile in tilesetInformation)
			{
				NSString* tileID = [[tile attributeForName:@"id"] stringValue];
				
				NSArray *tileproperties = [tile nodesForXPath:@"/tile/properties/property" error:nil];
				for(DDXMLElement *property in tileproperties) 
				{
					NSMutableDictionary* tileinfo = [[[NSMutableDictionary alloc] init] retain];
					
					NSString *name = [[property attributeForName:@"name"] stringValue];
					NSString *value = [[property attributeForName:@"value"] stringValue];
#if defined(GAMENGINE_DEBUG)
					NSLog(@"TileMap: Tile property '%@' found with value '%@'", name, value);
#endif
					[tileinfo setObject:value forKey:name];
					NSLog(@"Adding info to dictionary about tile %d", [tileID intValue]);
					[_tileSetProperties setObject:tileinfo forKey:tileID];
					[tileinfo release];
				}
			}
		}			
		
		
		
		/*
		** Walk through all the layers and gether the information
		** required then create a new layer object.
		*/
		NSArray *layers = [theXMLDocument nodesForXPath:@"/map/layer" error:nil];
		Layer* newlayer = nil;
		
		
		_currentLayerID = 0;
		for (DDXMLElement* currentLayer in layers) 
		{
			
			NSString *layerName     = [[[currentLayer  attributeForName:@"name"] stringValue] retain];
			NSInteger layerWidth    = [[[currentLayer attributeForName:@"width"] stringValue] intValue];
			NSInteger layerHeight   = [[[currentLayer attributeForName:@"height"] stringValue] intValue];
			
			newlayer = [[[Layer alloc] initWithName:layerName 
										   layerID:_currentLayerID 
										layerWidth:layerWidth 
									   layerHeight:layerHeight] retain];

			

			
			if (newlayer)
			{
				
				[newlayer.layerProperties setObject:layerName forKey:@"name"];
				[newlayer.layerProperties setObject:[NSString stringWithFormat:@"%d",layerHeight] forKey:@"height"];
				[newlayer.layerProperties setObject:[NSString stringWithFormat:@"%d",layerWidth]  forKey:@"width"];
				
		
				NSArray *layerTiles = [currentLayer nodesForXPath:@"/layer/data/tile" error:nil];
				
				
				int maxTilesPerRow = ((layerWidth * _tileWidth) / _tileWidth) - 1; /* -1 to make it computer understandable 0,1,2 ... etc */ 
			//	NSLog(@"max == %d", maxTilesPerRow);

				int i = 0;
				int cnt = 0;
				
				_tileX = 0;
				_tileY = 0;
				
				for (DDXMLElement* tile in layerTiles) 
				{
					NSInteger globalID     = [[[tile attributeForName:@"gid"] stringValue] intValue];
//					NSLog(@"Adding %d to %d (%d, %d) ", globalID, [newlayer layerID],_tileX, _tileY);

					if(globalID == 0) {
						[newlayer addTileAtX:_tileX y:_tileY tileSetID:-1 tileID:0 globalID:0];
					} else {
						TileSet *tileSet = [self findTileSetWithGlobalID:globalID];
						//	NSLog(@"Add");
						//	NSLog(@"Adding %d to %d (%d, %d) ", globalID, [newlayer layerID],_tileX, _tileY);
						[newlayer addTileAtX:_tileX 
										   y:_tileY 
								   tileSetID:[tileSet tileSetID] 
									  tileID:globalID - [tileSet firstGID] 
									globalID:globalID];
					}
					
					if (maxTilesPerRow == cnt) {
						_tileX  = 0;
						_tileY ++;
						cnt     = 0;
						//cnt     ++;
					} else {
						cnt++ ;
						_tileX   = cnt;
					}

					
					i++;  
				}
			}
			_currentLayerID ++;
			
		//	NSLog(@"Adding layer %@ (%@)", layerName, newlayer);
			[_layers addObject:newlayer];
			[newlayer release];
			
		}
		// hier
		
		
		//NSLog(@"Parsing done");
		//NSLog(@"_mapProperties = %@",_mapProperties);
		//NSLog(@"_tileSetProperties = %@",_tileSetProperties); 
		
	//	[fileContents release];
		
		
		
	}
	@catch (NSException * e) {
#if defined(GAMENGINE_DEBUG)
		NSLog(@"GameEngine: (TileMap::parseMapFile) %@", e);
#endif		
	}	
}
@end